package jsonpb

import (
	"net/url"
	"reflect"
	"strings"

	"gitee.com/xuyiping_admin/pkg/xerr"
	"gitee.com/xuyiping_admin/pkg/xreflect"

	"github.com/golang/protobuf/proto"
)

var emptyField = reflect.StructField{}

func UnmarshalQuery(values url.Values, pb proto.Message) error {
	target := reflect.ValueOf(pb).Elem()
	targetType := target.Type()
	if targetType.Kind() != reflect.Struct {
		return xerr.New("target should be struct")
	}
	sprops := proto.GetProperties(targetType)
	for i := 0; i < target.NumField(); i++ {
		ft := target.Type().Field(i)
		if strings.HasPrefix(ft.Name, "XXX_") {
			continue
		}
		getField := func(prop *proto.Properties) ([]string, bool) {
			// Be liberal in what names we accept; both orig_name and camelName are okay.
			camel, orig := prop.JSONName, prop.OrigName

			keys := []string{camel, orig}
			// handle condition xxx[]=1&xxx[]=2
			if prop.Repeated {
				keys = append(keys, camel+"[]", orig+"[]")
			}
			for _, key := range keys {
				v, ok := values[key]
				if ok {
					return v, true
				}
			}
			return nil, false
		}

		valueForField, ok := getField(sprops.Prop[i])
		if !ok {
			continue
		}
		err := setProtoByQueryValue(target.Field(i), valueForField, sprops.Prop[i])
		if err != nil {
			return err
		}
	}
	return nil
}

func setProtoByQueryValue(target reflect.Value, queryValue []string, prop *proto.Properties) (err error) {
	if len(queryValue) == 0 {
		if prop.HasDefault {
			queryValue = []string{prop.Default}
		} else {
			return nil
		}
	}
	if target.Kind() == reflect.Slice {
		slice := reflect.MakeSlice(target.Type(), len(queryValue), len(queryValue))
		for i, s := range queryValue {
			err := setProtoByQueryValue(slice.Index(i), []string{s}, prop)
			if err != nil {
				return err
			}
		}
		target.Set(slice)
		return nil
	}

	// Handle enums, which have an underlying type of int32,
	// and may appear as strings.
	// The case of an enum appearing as a number is handled
	// at the bottom of this function.
	if prop != nil && prop.Enum != "" {
		vmap := proto.EnumValueMap(prop.Enum)
		if len(queryValue) == 1 {
			// Don't need to do unquoting; valid enum names
			// are from a limited character set.
			s := queryValue[0]
			n, ok := vmap[s]
			if !ok {
				return xerr.Errorf("unknown value %q for enum %s", s, prop.Enum)
			}
			return setProtoEnum(n, target, prop)
		}
	}
	return xreflect.SetString(target, queryValue[0])
}

func setProtoEnum(val int32, target reflect.Value, prop *proto.Properties) error {
	if target.Kind() == reflect.Ptr { // proto2
		target.Set(reflect.New(target.Type().Elem()))
		target = target.Elem()
	}
	if target.Kind() != reflect.Int32 {
		return xerr.Errorf("invalid target %q for enum %s", target.Kind(), prop.Enum)
	}
	target.SetInt(int64(val))
	return nil
}
